/**
  * @file  : eeprom.c
  * @brief : read/write eeprom(24cXX) using HAL interface.
  */

/************************* INCLUDE ***********************/
#include "main.h"
#include "i2c.h"
#include "cmsis_os.h"
/*********************** INCLUDE END *********************/

/************************ TYPEDEF ************************/
typedef struct {
  uint16_t totalSize; // in bytes
  uint8_t pageSize;   // in bytes
  uint8_t writeCycle; // in milliseconds
  uint16_t pageCount;
  uint8_t deviceAddres;
  QueueHandle_t flagQ;
  SemaphoreHandle_t lock;
  TickType_t writedTime; // write time
} EEPROM_t;
/********************** TYPEDEF END **********************/

/************************ CONSTANT ***********************/
#define EEPROM_FREE  0
#define EEPROM_BUSY  1
#define EEPROM_DONE  2
#define EEPROM_ERROR 3
/********************** CONSTANT END *********************/
  
/************************* MACRO *************************/
/*********************** MACRO END ***********************/
  
/************************* EXTERN ************************/
/*********************** EXTERN END **********************/
  
/********************* LOCAL FUNCTION ********************/
__STATIC_INLINE void EEPROM_QueueSendFromISR(uint8_t state);
static HAL_StatusTypeDef EEPROM_PageWriteBytes(uint16_t addr, uint8_t *pData, uint8_t size, uint32_t timeout);
/******************* LOCAL FUNCTION END ******************/

/********************* LOCAL VARIABLE ********************/
static EEPROM_t eeprom = {
    .totalSize = 1024,
    .pageSize = 16,
    .writeCycle = 3,
    .pageCount = 0,
    .deviceAddres = 0xA0,
    .flagQ = NULL,
    .lock = NULL
};
/******************* LOCAL VARIABLE END ******************/

void EEPROM_Init(void)
{
  MX_I2C1_Init();

  eeprom.pageCount = eeprom.totalSize / eeprom.pageSize;

  eeprom.flagQ = xQueueCreate(4, sizeof(uint8_t));
  if (eeprom.flagQ == NULL) {
    Error_Handler();
  }
  eeprom.lock = xSemaphoreCreateBinary();
  if (eeprom.lock == NULL) {
    Error_Handler();
  } else {
    xSemaphoreGive(eeprom.lock);
  }
}

HAL_StatusTypeDef EEPROM_WriteBytes(uint16_t addr, uint8_t *pData, uint16_t size, uint32_t timeout)
{
  uint32_t beginTicks;
  uint32_t deltaTicks;
  uint8_t pageLeft;
  HAL_StatusTypeDef ret;

  if (addr + size > eeprom.totalSize)
    return HAL_ERROR;

  // 记录开始时间
  beginTicks = HAL_GetTick();

  // 获取锁
  if (xSemaphoreTake(eeprom.lock, pdMS_TO_TICKS(timeout)) != pdPASS)
    return HAL_TIMEOUT;

  // 如果不是从页首开始，就先写入多余的字节，保证下次从页首开始写
  pageLeft = eeprom.pageSize - addr % eeprom.pageSize;
  if (pageLeft) {
    // 计算超时
    deltaTicks = HAL_GetTick() - beginTicks;
    if (deltaTicks >= timeout) {
      xSemaphoreGive(eeprom.lock);
      return HAL_TIMEOUT;
    }

    // 写入多余字节
    ret = EEPROM_PageWriteBytes(addr, pData, pageLeft, timeout - deltaTicks);
    if (ret != HAL_OK) {
      xSemaphoreGive(eeprom.lock);
      return ret;
    } else {
      // 数据调整
      addr += pageLeft;
      pData += pageLeft;
      size -= pageLeft;
    }
  }

  // 循环写入
  while (size > 0) {
    // 计算超时
    deltaTicks = HAL_GetTick() - beginTicks;
    if (deltaTicks >= timeout) {
      xSemaphoreGive(eeprom.lock);
      return HAL_TIMEOUT;
    }

    // 是否需要页写
    if (size > eeprom.pageSize) {
      // 按页写
      ret = EEPROM_PageWriteBytes(addr, pData, eeprom.pageSize, timeout - deltaTicks);
      if (ret != HAL_OK) {
        xSemaphoreGive(eeprom.lock);
        return ret;
      } else {
        addr += eeprom.pageSize;
        pData += eeprom.pageSize;
        size -= eeprom.pageSize;
      }
    } else {
      // 写入剩余数据
      ret = EEPROM_PageWriteBytes(addr, pData, size, timeout - deltaTicks);
      if (ret != HAL_OK) {
        xSemaphoreGive(eeprom.lock);
        return ret;
      } else {
        size = 0;
      }
    }
  }

  xSemaphoreGive(eeprom.lock);
  return HAL_OK;
}

static HAL_StatusTypeDef EEPROM_PageWriteBytes(uint16_t addr, uint8_t *pData, uint8_t size, uint32_t timeout)
{
  TickType_t deltaTicks;
  HAL_StatusTypeDef ret;
  uint16_t devAddr;
  uint8_t flag;

  devAddr = (addr & 0x0300) >> 8;
  devAddr |= (uint16_t)eeprom.deviceAddres & 0xFF;

  xQueueReset(eeprom.flagQ);

  ret = HAL_I2C_Mem_Write_DMA(&hi2c1, devAddr, addr & 0xFF, I2C_MEMADD_SIZE_8BIT, pData, size);
  if (ret != HAL_OK) {
    return ret;
  }

  if (xQueueReceive(eeprom.flagQ, &flag, pdMS_TO_TICKS(timeout)) != pdPASS) {
    xSemaphoreGive(eeprom.lock);
    return HAL_TIMEOUT;
  } else if (flag == EEPROM_ERROR) {
    MY_IIC_ReInit();
    xSemaphoreGive(eeprom.lock);
    return HAL_ERROR;
  }

  deltaTicks = eeprom.writedTime - xTaskGetTickCount();
  if (deltaTicks < eeprom.writeCycle) {
    vTaskDelay(eeprom.writeCycle - deltaTicks);
  }
  return HAL_OK;
}

HAL_StatusTypeDef EEPROM_ReadBytes(uint16_t addr, uint8_t *pData, uint16_t size, uint32_t timeout)
{
  TickType_t deltaTicks;
  uint8_t flag;
  HAL_StatusTypeDef ret;
  uint16_t devAddr;

  if (addr + size > eeprom.totalSize)
    return HAL_ERROR;

  deltaTicks = xTaskGetTickCount();

  if (xSemaphoreTake(eeprom.lock, pdMS_TO_TICKS(timeout)) != pdPASS)
      return HAL_TIMEOUT;

  devAddr = (addr & 0x0300) >> 8;
  devAddr |= (uint16_t)eeprom.deviceAddres & 0xFF;

  xQueueReset(eeprom.flagQ);

  ret = HAL_I2C_Mem_Read_DMA(&hi2c1, devAddr, addr & 0xFF, I2C_MEMADD_SIZE_8BIT, pData, size);
  if (ret != HAL_OK) {
    xSemaphoreGive(eeprom.lock);
    return ret;
  }

  deltaTicks = xTaskGetTickCount() - deltaTicks;
  if (deltaTicks >= pdMS_TO_TICKS(timeout)) {
    xSemaphoreGive(eeprom.lock);
    return HAL_TIMEOUT;
  } else {
    deltaTicks = pdMS_TO_TICKS(timeout) - deltaTicks;
  }

  if (xQueueReceive(eeprom.flagQ, &flag, deltaTicks) != pdPASS) {
    xSemaphoreGive(eeprom.lock);
    return HAL_TIMEOUT;
  } else if (flag == EEPROM_ERROR) {
    MY_IIC_ReInit();
    xSemaphoreGive(eeprom.lock);
    return HAL_ERROR;
  } else {
    xSemaphoreGive(eeprom.lock);
    return HAL_OK;
  }
}

void HAL_I2C_MemTxCpltCallback(I2C_HandleTypeDef *hi2c)
{
  if (hi2c == &hi2c1) {
    eeprom.writedTime = xTaskGetTickCountFromISR();
    EEPROM_QueueSendFromISR(EEPROM_DONE);
  }
}

void HAL_I2C_MemRxCpltCallback(I2C_HandleTypeDef *hi2c)
{
  if (hi2c == &hi2c1) {
    EEPROM_QueueSendFromISR(EEPROM_DONE);
  }
}

void HAL_I2C_ErrorCallback(I2C_HandleTypeDef *hi2c)
{
  if (hi2c == &hi2c1) {
    EEPROM_QueueSendFromISR(EEPROM_ERROR);
  }
}

__STATIC_INLINE void EEPROM_QueueSendFromISR(uint8_t state)
{
  BaseType_t woken = pdFALSE;

  xQueueSendFromISR(eeprom.flagQ, &state, &woken);
  portYIELD_FROM_ISR(woken);
}
